package com.dingwen.treasure.task.quartz.manager;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.dingwen.treasure.base.constant.QuartzConstant;
import com.dingwen.treasure.base.enums.QuartzStateEnum;
import com.dingwen.treasure.base.exception.BusinessException;
import com.dingwen.treasure.jpa.service.QuartzInfoService;
import com.dingwen.treasure.model.entity.jpa.QuartzInfo;
import com.dingwen.treasure.task.quartz.dto.QuartzInfoStateDTO;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.matchers.EverythingMatcher;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.data.domain.Example;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.function.Predicate;

/**
 * quartz job 管理器
 *
 * @author dingwen
 * @date 2022/03/13
 */
@Slf4j
@Component
public class QuartzJobManager {

    /**
     * 调度器
     */
    @Resource
    private Scheduler scheduler;

    /**
     * 全局监听存储日志
     */
    @Resource
    private JobListener quartzLogListener;


    /**
     * 定时任务job服务
     */
    @Resource
    private QuartzInfoService quartzInfoService;


    /**
     * 初始化所有任务
     */
    public void init() {
        // 获取所有启用状态的定时任务信息
        QuartzInfo quartzInfo = QuartzInfo.builder().state(QuartzStateEnum.ENABLE).build();
        List<QuartzInfo> quartzInfos = quartzInfoService.findAll(Example.of(quartzInfo));

        if (CollectionUtil.isEmpty(quartzInfos)) {
            log.info("当前没有定时任务需要执行");
            return;
        }
        // 添加工作任务
        quartzInfos.parallelStream().forEach(info -> {
            try {
                initJob(info);
                log.info("定时任务初始化成功，共添加{}个任务", quartzInfos.size());
            } catch (Exception e) {
                log.error("定时任务初始化失败，错误信息{}", e.getMessage());
                System.exit(-1);
            }
        });
    }


    /**
     * 初始化添加job
     *
     * @param quartzInfo 信息
     * @throws Exception 异常
     */
    public void initJob(QuartzInfo quartzInfo) {
        try {
            // 定时任务执行类字节码
            Class<?> clazz = Class.forName(quartzInfo.getFullClassName()).newInstance().getClass();
            deleteJob(quartzInfo);
            addJob(clazz, quartzInfo);
            quartzInfoService.updateById(quartzInfo);
        } catch (Exception exception) {
            throw new BusinessException(StrUtil.format("添加定时任务发生异常：\n", JSONUtil.toJsonStr(quartzInfo)));
        }
    }

    /**
     * 添加job
     *
     * @param quartzInfo 信息
     * @throws Exception 异常
     */
    public void addJob(QuartzInfo quartzInfo) {
        try {
            judgeExists(quartzInfo, optional -> !optional.isPresent());
            // 定时任务执行类字节码
            Class<?> clazz = Class.forName(quartzInfo.getFullClassName()).newInstance().getClass();
            addJob(clazz, quartzInfo);
        } catch (Exception exception) {
            throw new BusinessException(StrUtil.format("添加定时任务发生异常：\n", JSONUtil.toJsonStr(quartzInfo)));
        }
    }

    /**
     * 添加job
     *
     * @param clazz      clazz
     * @param quartzInfo 信息
     * @throws Exception 异常
     */
    public void addJob(Class<?> clazz, QuartzInfo quartzInfo) throws Exception {


        // 适配 QuartzInfo
        quartzInfo = adapterQuartzInfo(quartzInfo);

        // 启动调度器
        scheduler.start();

        //构建job信息
        JobDetail jobDetail = JobBuilder.newJob(((Job) clazz.newInstance()).getClass())
                .withIdentity(quartzInfo.getJobName(), quartzInfo.getJobGroupName())
                .build();

        //表达式调度构建器(即任务执行的时间)
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzInfo.getCronExpression());

        //按新的cronExpression表达式构建一个新的trigger
        CronTrigger trigger = TriggerBuilder
                .newTrigger()
                .withIdentity(quartzInfo.getTriggerName(), quartzInfo.getTriggerGroupName())
                .withSchedule(scheduleBuilder).build();

        //构建JobDataMap，写入数据
        JobDataMap jobDataMap = buildJobDataMap(quartzInfo);
        jobDetail.getJobDataMap().putAll(jobDataMap);

        scheduler.scheduleJob(jobDetail, trigger);

        // 添加全局监听（实现失败成功计数、日志记录）
        scheduler.getListenerManager()
                .addJobListener(quartzLogListener, EverythingMatcher.allJobs());

        log.info("定时任务：{},cron 表达式:{}, 服务器当前时区:{}，jobDataMap: \n{}",
                quartzInfo.getName(),
                quartzInfo.getCronExpression(),
                TimeZone.getDefault().getID(),
                JSONUtil.toJsonPrettyStr(jobDataMap)
        );
    }


    /**
     * 适配器任务信息： 当 triggerName 、triggerGroupName 、jobGroupName、jobName 为空是默认设置 code 值
     *
     * @param quartzInfo 任务信息
     * @return {@link QuartzInfo}
     */
    public QuartzInfo adapterQuartzInfo(QuartzInfo quartzInfo) {
        //jobName
        if (StrUtil.isBlank(quartzInfo.getJobName())) {
            quartzInfo.setJobName(quartzInfo.getCode());
        }

        //jobGroupName
        if (StrUtil.isBlank(quartzInfo.getJobGroupName())) {
            quartzInfo.setJobGroupName(quartzInfo.getCode());
        }

        // triggerName
        if (StrUtil.isBlank(quartzInfo.getTriggerName())) {
            quartzInfo.setTriggerName(quartzInfo.getCode());
        }

        //triggerGroupName
        if (StrUtil.isBlank(quartzInfo.getTriggerGroupName())) {
            quartzInfo.setTriggerGroupName(quartzInfo.getCode());
        }
        return quartzInfo;

    }

    /**
     * 删除工作: 删除quartz info
     *
     * @param quartzInfo 任务信息
     * @throws Exception 异常
     */
    public void deleteJob(QuartzInfo quartzInfo) {

        try {
            TriggerKey triggerKey = buildTriggerKey(quartzInfo);
            JobKey jobKey = buildJobKey(quartzInfo);
            // 暂停触发器
            scheduler.pauseTrigger(triggerKey);
            // 卸载已装配到调度器的触发器
            scheduler.unscheduleJob(triggerKey);
            // 删除工作任务
            scheduler.deleteJob(jobKey);
            String message = "删除定时任务：\n{},成功";
            log.info(message, JSONUtil.toJsonPrettyStr(quartzInfo));
        } catch (SchedulerException e) {
            throw new BusinessException(StrUtil.format("定时任务删除发生异常：\n", JSONUtil.toJsonPrettyStr(quartzInfo)));
        }
    }

    /**
     * 修改工作任务
     *
     * @param quartzInfo 信息
     */
    public void modifyJob(QuartzInfo quartzInfo) {
        try {
            // 判断
            Optional<JobDetail> jobDetailOptional = judgeExists(quartzInfo, Optional::isPresent);
            Class<? extends Job> jobClass = jobDetailOptional.get().getJobClass();

            // 删除
            deleteJob(quartzInfo);
            //添加任务
            addJob(jobClass, quartzInfo);
            // 修改quartzInfo
            quartzInfoService.updateById(quartzInfo);
            log.info("修改任务成功：\n{}", JSONUtil.toJsonStr(quartzInfo));
        } catch (Exception exception) {
            throw new BusinessException(StrUtil.format("定时任务修改发生异常：\n", JSONUtil.toJsonStr(quartzInfo)));
        }
    }

    /**
     * 判读是否存在： 修改必须事先存在，新增必须不存在
     *
     * @param quartzInfo 任务信息
     * @return {@link Class}<{@link ?} {@link extends} {@link Job}>
     * @throws SchedulerException 计划程序异常
     */
    public Optional<JobDetail> judgeExists(QuartzInfo quartzInfo, Predicate<Optional> predicate) throws Exception {

        // 适配
        QuartzInfo quartzInfoForAdapter = adapterQuartzInfo(quartzInfo);
        TriggerKey triggerKey = buildTriggerKey(quartzInfoForAdapter);
        JobKey jobKey = buildJobKey(quartzInfoForAdapter);

        Optional<CronTrigger> optionalCronTrigger = Optional.ofNullable((CronTrigger) scheduler.getTrigger(triggerKey));

        Optional<JobDetail> jobDetailOptional = Optional.ofNullable(scheduler.getJobDetail(jobKey));

        if (!(predicate.test(optionalCronTrigger) && predicate.test(jobDetailOptional))) {
            String template = "新增或修改任务信息异常: 触发器：\n{} \n任务：\n{}";
            String message = StrUtil.format(template, JSONUtil.toJsonStr(triggerKey), JSONUtil.toJsonPrettyStr(jobKey));
            log.warn(message);
            throw new BusinessException(message);
        }
        return jobDetailOptional;
    }

    /**
     * 现在就开始工作
     * 立即执行任务
     *
     * @param quartzId 定时任务 id
     * @throws SchedulerException 调度程序异常
     */
    public void startJobNow(Long quartzId) {
        String template = "quartzId: {},立即执行失败，请确保任务处于启用状态";
        String message = StrUtil.format(template, quartzId);
        try {
            Optional<QuartzInfo> quartzInfoOptional = quartzInfoService.findById(quartzId);
            QuartzInfo quartzInfo = quartzInfoOptional.get();
            Assert.isTrue(quartzInfo.getState() == QuartzStateEnum.ENABLE, message);
            JobDataMap jobDataMap = buildJobDataMap(quartzInfo);
            JobKey jobKey = buildJobKey(quartzInfo);
            scheduler.triggerJob(jobKey, jobDataMap);
        } catch (SchedulerException e) {
            throw new BusinessException(message);
        }
    }


    /**
     * 获取所有执行中的任务列表
     *
     * @return 结果
     * @throws SchedulerException 异常
     */
    public List<Map<String, Object>> getAllJob() {
        try {
            // 获取所有的JobKey
            GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
            Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
            // 获取所有的Job
            List<Map<String, Object>> jobList = new ArrayList<>();

            for (JobKey jobKey : jobKeys) {
                // 获取所有的Trigger
                List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
                for (Trigger trigger : triggers) {
                    Map<String, Object> job = new HashMap<>(16);
                    job.put(QuartzConstant.JOB_NAME, jobKey.getName());
                    job.put(QuartzConstant.JOB_GROUP_NAME, jobKey.getGroup());
                    job.put(QuartzConstant.TRIGGER, trigger.getKey());
                    Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
                    job.put(QuartzConstant.JOB_STATUS, triggerState.name());
                    if (trigger instanceof CronTrigger) {
                        CronTrigger cronTrigger = (CronTrigger) trigger;
                        String cronExpression = cronTrigger.getCronExpression();
                        job.put(QuartzConstant.CRON_EXPRESSION, cronExpression);
                    }
                    jobList.add(job);
                }
            }
            return jobList;
        } catch (SchedulerException e) {
            throw new BusinessException(StrUtil.format("定时任务：获取所有执行中的任务列表发生异常，message: {}", e.getMessage()));
        }
    }

    /**
     * 修改任务状态
     *
     * @param quartzInfoStateDTO 任务信息
     */
    @Transactional(rollbackFor = Exception.class)
    public void modifyJobState(QuartzInfoStateDTO quartzInfoStateDTO) {

        Optional<QuartzInfo> quartzInfoOptional = quartzInfoService.findById(quartzInfoStateDTO.getId());
        quartzInfoOptional.orElseThrow(() -> {
            String message = "id: {},定时任务不存在，修改状态失败";
            return new BusinessException(StrUtil.format(message, quartzInfoStateDTO.getId()));
        });

        QuartzInfo quartzInfo = quartzInfoOptional.get();

        switch (quartzInfoStateDTO.getState()) {
            // 启用
            case ENABLE:
                deleteJob(quartzInfo);
                addJob(quartzInfo);
                break;
            // 禁用
            case DISABLE:
                deleteJob(quartzInfo);
                break;
            default:
                break;
        }
        quartzInfoService.updateById(quartzInfo);

    }


    /**
     * 构建工作数据地图
     *
     * @param quartzInfo 定时任务 info
     * @return {@link Map}
     */
    private JobDataMap buildJobDataMap(QuartzInfo quartzInfo) {
        JobDataMap jobDataMap = new JobDataMap();
        String jobDataMapJson = quartzInfo.getJobDataMap();
        if (StringUtils.hasText(jobDataMapJson) && JSONUtil.isJson(jobDataMapJson)) {
            jobDataMap.putAll(JSONUtil.toBean(jobDataMapJson, Map.class));
        }
        jobDataMap.put(QuartzConstant.QUARTZ_ID, quartzInfo.getId());
        return jobDataMap;
    }

    /**
     * buildTriggerKey
     *
     * @param quartzInfo
     * @return
     */
    private TriggerKey buildTriggerKey(QuartzInfo quartzInfo) {
        return TriggerKey.triggerKey(quartzInfo.getTriggerName(), quartzInfo.getTriggerGroupName());
    }

    /**
     * buildJobKey
     *
     * @param quartzInfo
     * @return
     */
    private JobKey buildJobKey(QuartzInfo quartzInfo) {
        return JobKey.jobKey(quartzInfo.getJobName(), quartzInfo.getJobGroupName());
    }
}
